/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.google.code.peersim.pastry.protocol;

import java.util.UUID;
import peersim.core.CommonState;

/**
 * Representation of a Pastry message.
 *
 * @author frusso
 * @version 0.1
 * @since 0.1
 */
public abstract class PastryMessage {

  private final UUID id;
  private final long timestamp;
  private Type type;
  private PastryNode originalSender;
  private PastryNode src;
  private PastryNode dst;
  private PastryId key;
  private int hops = 0;
  private int hashCode;

  /**
   * Constructor. Assigns every message a unique randomly generated identifier, and
   * a timestamp.
   */
  public PastryMessage() {
    id = UUID.randomUUID();
    timestamp = CommonState.getTime();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object obj) {
    if(this==obj)
      return true;
    if(!(obj instanceof PastryMessage))
      return false;
    PastryMessage other = (PastryMessage) obj;
    return id.equals(other.id) && timestamp==other.timestamp && type.equals(other.type) &&
            originalSender.equals(other.originalSender) && src.equals(other.src) && dst.equals(other.dst)
            && hops==other.hops;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    int res = hashCode;
    if(res==0) {
      res = 17;
      res = 31*res + id.hashCode();
      res = 31*res + type.hashCode();
      res = 31*res + originalSender.hashCode();
      res = 31*res + src.hashCode();
      res = 31*res + dst.hashCode();
      res = 31*res + hops;
      hashCode = res;
    }
    return res;
  }

  /**
   * Returns the destination.
   * @return The destination
   */
  public PastryNode getDst() {
    return dst;
  }

  /**
   * Returns the number of hops this message has traveled.
   * 
   * @return The number of hops
   */
  public int getHops() {
    return hops;
  }

  /**
   * This message unique id.
   *
   * @return The unique id
   */
  public UUID getId() {
    return id;
  }

  /**
   * Returns the key if any.
   *
   * @return The key or {@code null}
   */
  public PastryId getKey() {
    return key;
  }

  /**
   * Returns the message original sender.
   *
   * @return The original sender
   */
  public PastryNode getOriginalSender() {
    return originalSender;
  }

  /**
   * Returns the sender.
   * @return The sender
   */
  public PastryNode getSrc() {
    return src;
  }

  /**
   * The moment in time when this message was created.
   * 
   * @return The timestamp
   */
  public long getTimestamp() {
    return timestamp;
  }

  /**
   * Returns the message type.
   * @return The type
   */
  public Type getType() {
    return type;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return type.toString()+", hops "+hops+" key "+key+" from "+src.getPastryId()+" to "+dst.getPastryId();
  }

  /**
   * Increases by one the number of hops.
   */
  void increaseHops() {
    setHops(getHops() + 1);
  }

  /**
   * Sets the destination.
   *
   * @param node The destination
   */
  protected void setDestination(PastryNode node) {
    this.dst = node;
  }

  /**
   * Sets the key.
   *
   * @param key The key
   */
  protected void setKey(PastryId key) {
    this.key = key;
  }

  /**
   * Sets the originator.
   *
   * @param node The originator
   */
  protected void setOrignator(PastryNode node) {
    this.originalSender = node;
  }

  /**
   * Sets the sender.
   *
   * @param node The sender
   */
  protected void setSender(PastryNode node) {
    this.src = node;
  }

  /**
   * Sets the type.
   *
   * @param type The type
   */
  protected void setType(Type type) {
    this.type = type;
  }

  /**
   * Sets the number of hops.
   * @param hops The number of hops
   */
  protected void setHops(int hops) {
    this.hops = hops;
  }

  /**
   * Classification of the legal Pastry message types.
   *
   * @author frusso
   * @version 0.1
   * @since 0.1
   */
  public enum Type {
    /**
     * A message used to join a Pastry network.
     */
    JOIN_REQ,
    REFRESH_STATE_TABLES,
    /**
     * A message used to send Pastry state-tables to other nodes.
     */
    STATE_TABLES,
    /**
     * A message that confirms that a joining node has finally joined the Pastry network.
     */
    JOIN_OK,
    /**
     * A message used to spread a resource in the pastry network.
     */
    SEND_RESOURCE,
    /**
     * A message used to ack the reception of a {@link Type#SEND_RESOURCE} message.
     */
    SEND_RESOURCE_ACK,
    /**
     * A message used to look for a resource in the pastry network.
     */
    LOOKUP_RESOURCE,
    /**
     * A message used to ack the reception of a {@link Type#LOOKUP_RESOURCE} message.
     */
    LOOKUP_RESOURCE_ACK,
    /**
     * A message used to ask for a routing table entry replacement.
     */
    ROUTING_TABLE_ENTRY_REQ,
    /**
     * A message ued to aswer to a routing table entry replacement.
     */
    ROUTING_TABLE_ENTRY_RSP,
    /**
     * A message used to ask for a leaf-set entry replacement.
     */
    LEAF_SET_ENTRY_REQ,
    /**
     * A message ued to aswer to a leaf-set entry replacement.
     */
    LEAF_SET_ENTRY_RSP;
  }
}